#include <TaskScheduler.h>
#include <TaskSchedulerDeclarations.h>
#include <TaskSchedulerSleepMethods.h>

#include <Arduino.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <ESP8266WiFi.h>
// #include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>

#include <IRremoteESP8266.h>
#include <IRsend.h>
// #include <ir_Gree.h>
#include<ir_Kelvinator.h>

// ESP8266WiFiMulti wifiMulti;
ESP8266WebServer esp8266_server(80);

//---------------------------------------------------------------
#define STASSID "ESP6266-XDS"
#define STAPSK  "88888888"

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define DHTPIN 2
#define DHTTYPE DHT22

#define MIN_TEMP 16
#define MAX_TEMP 28
#define DELTA_TEMP 1.0
#define SEND_CMD_DELAY 1000
#define SEND_CMD_COUNT 3

#define AC_STATUS_COOLING "COOLING"
#define AC_STATUS_HEATING "HEATING"
#define AC_STATUS_SWEEPING "SWEEPING"
#define AC_STATUS_OFF "OFF"
#define AC_STATUS_ON "ON"
//---------------------------------------------------------------

DHT dht(DHTPIN, DHTTYPE);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

const uint16_t kIrLed = 14;  //14 -->D5 //offical IR LED pin 4
// IRGreeAC ac(kIrLed); // Set the GPIO to be used to sending the message to the AC
IRKelvinatorAC ac(kIrLed);

float temperature;
float humidity;

char temperatureStr[10];
char humidityStr[10];

float ct=0; //config temperature
char ctStr[10];

char* ac_status = "---";
int flag = 1;
bool IS_AC_RUNNING = false;
bool IS_AC_HEATING = false;


void printState() 
{
  // Display the settings.
  Serial.println("GREE A/C remote is in the following state:");
  Serial.printf("  %s\n", ac.toString().c_str());
  // Display the encoded IR sequence.
  unsigned char* ir_code = ac.getRaw();
  Serial.print("IR Code: 0x");
  for (uint8_t i = 0; i < kGreeStateLength; i++)
    Serial.printf("%02X", ir_code[i]);
  Serial.println();
}

// Start a webserver
void task0Callback() {
  // Serial.println("--------------------------------------------");
  // Serial.println("task0Callback: Start a webserver...");
  // Serial.println("--------------------------------------------");

  esp8266_server.handleClient();
}


// Read data from AM2301A 
void task1Callback() {
  Serial.println("--------------------------------------------");
  Serial.println("task1Callback: Read data from AM2301A...");
  Serial.println("--------------------------------------------");

  temperature = dht.readTemperature();
  humidity = dht.readHumidity();

  dtostrf(temperature, 6, 2, temperatureStr);
  dtostrf(humidity, 6, 2, humidityStr);

  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.print("C");
  Serial.print(" Humidity ");
  Serial.print(humidity);
  Serial.println("%");
}


//Show data on .96LED
void task2Callback() {
  Serial.println("--------------------------------------------");
  Serial.println("task2Callback: Show data on .96LED...");
  Serial.println("--------------------------------------------");

  // Clear the display
  display.clearDisplay();
  display.display();

  flag = flag % 2;

  // show temp and hum
  if (flag == 0) 
  {
    // show temp-----------------------------------------------
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Temperature: ");
    display.setTextSize(2);
    display.setCursor(0, 15);
    // display.print(28.6);
    display.print(temperature);

    display.print(" ");
    display.setTextSize(1);
    display.cp437(true);
    display.write(167);
    display.setTextSize(2);
    display.print("C");

    // show humidity--------------------------------------------
    display.setTextSize(1);
    display.setCursor(0, 0+35);
    display.print("Humidity: ");
    display.setTextSize(2);
    display.setCursor(0, 15+35);
    // display.print(46);
    display.print(humidity);
    display.print(" %");
  }

  //show current config value and AC status
  if (flag == 1) 
  {
    // show current config-----------------------------------------------
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Target: ");
    display.setTextSize(2);
    display.setCursor(0, 15);
    // display.print(28.6);
    display.print(ct);

    display.print(" ");
    display.setTextSize(1);
    display.cp437(true);
    display.write(167);
    display.setTextSize(2);
    display.print("C");

    // show AC Status--------------------------------------------
    display.setTextSize(1);
    display.setCursor(0, 0+35);
    display.print("Status: ");
    display.setTextSize(2);
    display.setCursor(0, 14+35);
    // display.print(46);
    display.print(ac_status);
  }
  flag++;

  display.display();
}


//Controll AC, Send Commands to AC
void task3Callback() {
  Serial.println("--------------------------------------------");
  Serial.println("task3Callback: Send Commands to Ctrl AC...");
  Serial.println("--------------------------------------------");

  if (ct == 0) 
  {
    Serial.println("ct is 0, do nothing...");
    return;
  }
  else
  {
    //set AC on
    if(!IS_AC_RUNNING)
    {
      IrApi_setAcON();    
      Serial.print("ct: ");
      Serial.println(ct);
      IS_AC_RUNNING = true;
    }
  }

  //AC is on
  if( abs(temperature - ct) < DELTA_TEMP ) // temperature is equal to ct; do sweeping
  {
    //send commands to AC
    IrApi_setSweeping();
  }
  else if( temperature < ct ) // temperature is less than ct, do heating
  {
    //send commands to AC
    IrApi_setHeating();
  }
  else if( temperature > ct) // temperature is greater than ct, do cooling
  {
    //send commands to AC
    IrApi_setCooling();
  }
  else
  {
    //do nothing
  }
  Serial.print("current temperature: ");
  Serial.println(temperature);
}


//---------------------------------------------------------------
// ac.on();
// // ac.setFan(1);
// // // kGreeAuto, kGreeDry, kGreeCool, kGreeFan, kGreeHeat
// // ac.setMode(kGreeCool);
// // ac.setTemp(20);  // 16-30C
// // ac.setSwingVertical(true, kGreeSwingAuto);
// // ac.setXFan(false);
// // ac.setLight(false);
// // ac.setSleep(false);
// // ac.setTurbo(false);
// // Send the command
// ac.send();
void IrApi_setAcON()
{
  Serial.println("IrApi_setAcON...");

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    ac.on();
    ac.setLight(true);
    ac.setFan(1);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }
}

void IrApi_setAcOFF()
{
  Serial.println("IrApi_setAcOFF...");
  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    ac.off();
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }
}

void IrApi_setHeating()
{
  Serial.println("IrApi_setHeating...");

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    // ac.setMode(kGreeHeat);
    ac.setMode(kKelvinatorHeat);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    ct = MAX_TEMP;
    dtostrf(ct, 6, 2, ctStr);

    ac.setTemp(MAX_TEMP);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }
  ac_status = AC_STATUS_HEATING;
  Serial.println("===================");
  Serial.println(ac_status);
  Serial.println("===================");
}

void IrApi_setCooling()
{
  Serial.println("IrApi_setCooling...");

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    // ac.setMode(kGreeCool);
    ac.setMode(kKelvinatorCool);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    ct = MIN_TEMP;
    dtostrf(ct, 6, 2, ctStr);

    ac.setTemp(MIN_TEMP);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  } 
  ac_status = AC_STATUS_COOLING;
}

void IrApi_setSweeping()
{
  Serial.println("IrApi_setSweeping...");

  for(int i=0; i<SEND_CMD_COUNT; i++)
  {
    // ac.setMode(kGreeFan);
    ac.setMode(kKelvinatorFan);
    ac.send();
    delay(SEND_CMD_DELAY);
    printState();
  }
  ac_status = AC_STATUS_SWEEPING;
}


//---------------------------------------------------------------
void handleRoot()
{
  Serial.println("a new request is coming...");
  String html = "";
  html+="<!DOCTYPE html>";
  html+="<html lang='en'>";
  html+="<head>";
  html+="    <meta charset='UTF-8'>";
  html+="    <meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  html+="    <title>ACCP</title>";
  html+="    <style>";
  html+="        .table {";
  html+="            font-size: 16px;";
  html+="            color: #666;";
  html+="            width: 240px;";
  html+="            margin: 15px auto;";
  html+="            padding: 15px 30px;";
  html+="            text-align: right;";
  html+="            border: #ccc 2px solid;";
  html+="        }";
  html+="        .info {";
  html+="            color: #000;";
  html+="            font-weight: bolder;";
  html+="        }";
  html+="        .button {";
  html+="            width: 240px;";
  html+="            display: block;";
  html+="            margin: 15px auto;";
  html+="            padding: 15px 30px; ";
  html+="            font-size: 16px;";
  html+="            color: #FFF;";
  html+="            text-align: center;";
  html+="            text-decoration: none;";
  html+="            border-radius: 5px;";
  html+="            cursor: pointer;";
  html+="            appearance: none;";
  html+="            border: none;";
  html+="            box-sizing: border-box;";
  html+="        }";
  html+="        .range{";
  html+="            width: 180px;";
  html+="            margin: 15px auto; ";
  html+="            padding: 15px 30px;";
  html+="            text-align: center;";
  html+="        }";
  html+="        .blue {";
  html+="            background-color: #1B9AF7;";
  html+="            border-color: #1B9AF7;";
  html+="        }";
  html+="        .orange {";
  html+="            background-color: #FEAE1B;";
  html+="            border-color: #FEAE1B;";
  html+="        }";
  html+="        .green{";
  html+="            background-color: #A5DE37;";
  html+="            border-color: #A5DE37;";
  html+="        }";
  html+="        .red{";
  html+="            background-color: #FF4351;";
  html+="            border-color: #FF4351;";
  html+="        }";
  html+="        @media (max-width: 600px) {";
  html+="            .button {";
  html+="                font-size: 14px;";
  html+="                padding: 10px 20px; /* Adjust padding for smaller screens */";
  html+="            }";
  html+="        }";
  html+="    </style>";
  html+="</head>";
  html+="<body>";
  html+="    <center><h3>空调控制面板</h3></center>";
  html+="    <hr>";
  html+="    <button id='setHeating' class='button orange'>加热</button>";
  html+="    <button id='setCooling' class='button blue'>制冷</button>";
  html+="    <button id='setSweeping' class='button blue'>扫风</button>";
  html+="    <button id='setAcON' class='button green'>开机</button>";
  html+="    <button id='setAcOFF' class='button red'>关机</button>";
  html+="    <hr>";
  html+="    <p class='range'>";
  html+="    <input type='range' min='16' max='28' step='1' value='" + String(ct) + "'/>";
  html+="    <output id='value'></output>";
  html+="    </p>";
  html+="    <button id='setTemperature' class='button blue'>目标温度设置</button>";
  html+="    <hr>";
  html+="    <center><p style='font-size: 18px;'>当前参数</p></center>";
  html+="    <table class='table'>";
  html+="        <tr>";
  html+="            <td width='64%'>温度：</td>";
  html+="            <td class='info'>" + String(temperatureStr) +"</td>";
  html+="        </tr>";
  html+="        <tr>";
  html+="            <td>湿度：</td>";
  html+="            <td class='info'>" + String(humidityStr) + "</td>";
  html+="        </tr>";
  html+="        <tr>";
  html+="            <td>目标温度：</td>";
  html+="            <td class='info'>" + String(ctStr) + "</td>";
  html+="        </tr>";
  html+="        <tr>";
  html+="            <td>运行状态：</td>";
  html+="            <td class='info'>" + String(ac_status) + "</td>";
  html+="        </tr>";
  html+="    </table>";
  html+="<script>";
  html+="    var range = document.querySelector(\"input[type='range']\");";
  html+="    var output = document.getElementById('value');";
  html+="    output.textContent = range.value;";
  html+="    range.addEventListener('input', function() {";
  html+="        output.textContent = range.value;";
  html+="    });";

  html+="    document.getElementById('setHeating').addEventListener('click', function() {";
  html+="    fetch('/setHeating', {";
  html+="        method: 'GET',";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => console.log(data))";
  html+="    .catch((error) => {";
  html+="        console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="    document.getElementById('setCooling').addEventListener('click', function() {";
  html+="    fetch('/setCooling', {";
  html+="        method: 'GET',";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => console.log(data))";
  html+="    .catch((error) => {";
  html+="        console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="    document.getElementById('setSweeping').addEventListener('click', function() {";
  html+="    fetch('/setSweeping', {";
  html+="        method: 'GET',";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => console.log(data))";
  html+="    .catch((error) => {";
  html+="        console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="    document.getElementById('setAcON').addEventListener('click', function() {";
  html+="    fetch('/setAcON', {";
  html+="        method: 'GET',";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => console.log(data))";
  html+="    .catch((error) => {";
  html+="        console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="    document.getElementById('setAcOFF').addEventListener('click', function() {";
  html+="    fetch('/setAcOFF', {";
  html+="        method: 'GET',";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => console.log(data))";
  html+="    .catch((error) => {";
  html+="        console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="    document.getElementById('setTemperature').addEventListener('click', function() {";
  html+="    fetch('/setTemperature', {";
  html+="    method: 'POST',";
  html+="    headers: {";
  html+="        'Content-Type': 'application/json',";
  html+="    },";
  html+="    body: JSON.stringify({";
  html+="        ct: range.value,";
  html+="    })";
  html+="    })";
  html+="    .then(response => response.json())";
  html+="    .then(data => {console.log(data); location.reload(true);})";
  html+="    .catch((error) => {";
  html+="    console.error('Error:', error);";
  html+="    });";
  html+="    });";

  html+="</script>";
  html+="</body>";
  html+="</html>";

  esp8266_server.send(200, "text/html", html);
}

void handleNotFound() 
{
  esp8266_server.send(404, "text/plain", "404: Not Found");
}



void setHeating() 
{
  Serial.println("setHeating...");
  IrApi_setHeating();

  StaticJsonDocument<200> doc;
  doc["state"] = "ok";
  doc["msg"] = "setHeating ok";
  String response;
  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}


void setCooling() 
{
  Serial.println("setCooling...");
  IrApi_setCooling();

  StaticJsonDocument<200> doc;
  doc["state"] = "ok";
  doc["msg"] = "setCooling ok";
  String response;
  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}


void setTemperature() 
{
  StaticJsonDocument<200> doc;
  String response;

  Serial.println("setTemperature...");
  // HERE ONLY RECORD CONFIG TEMPERATURE VALUE, DONOT SEND CMD TO AC!!!!!!
  // IrApi_setTemperature();

  if (esp8266_server.method() != HTTP_POST) 
  {
    doc["state"] = "fail";
    doc["msg"] = "setTemperature: Method Not Allowed";
  } 
  else 
  {
    DynamicJsonDocument doc1(1024);
    deserializeJson(doc1, esp8266_server.arg("plain"));

    String ctStr_tmp = doc1["ct"];
    // String ctStr_tmp = esp8266_server.arg("ct");
    Serial.print("ct: ");
    Serial.println(ctStr_tmp);
    doc["state"] = "ok";
    doc["msg"] = "setTemperature ok";
    ct = ctStr_tmp.toFloat();
    dtostrf(ct, 6, 2, ctStr);
  }

  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}


void setSweeping() 
{
  Serial.println("setSweeping...");
  IrApi_setSweeping();  

  StaticJsonDocument<200> doc;
  doc["state"] = "ok";
  doc["msg"] = "setSweeping ok";
  String response;
  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}

void setAcON()
{
  Serial.println("setAcON...");
  IrApi_setAcON();

  StaticJsonDocument<200> doc;
  doc["state"] = "ok";
  doc["msg"] = "setAcON ok";
  String response;
  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}

void setAcOFF() 
{
  Serial.println("setAcOFF...");
  IrApi_setAcOFF();

  StaticJsonDocument<200> doc;
  doc["state"] = "ok";
  doc["msg"] = "setAcON ok";
  String response;
  serializeJson(doc, response);
  esp8266_server.send(200, "application/json; charset=utf-8",  response);
}

// Create the tasks
Task task0(100, TASK_FOREVER, &task0Callback); 
Task task1(2000, TASK_FOREVER, &task1Callback); 
Task task2(6000, TASK_FOREVER, &task2Callback); 
Task task3(10000, TASK_FOREVER, &task3Callback); 

// Create the scheduler
Scheduler runner;
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT); 

  dht.begin();

  ac.begin();

  Serial.println("---------AP Mode------------");
  const char* ssid = STASSID;
  const char* password = STAPSK;

  WiFi.softAP(ssid, password);

  Serial.println("");
  Serial.print("AP SSID: ");
  Serial.println(ssid);

  Serial.print("AP IP: ");
  Serial.println(WiFi.softAPIP());
  //----------START WEBSERVER--------------------
 
  esp8266_server.on("/", handleRoot);

  esp8266_server.on("/setHeating", setHeating);
  esp8266_server.on("/setCooling", setCooling);
  esp8266_server.on("/setTemperature", setTemperature);
  esp8266_server.on("/setSweeping", setSweeping);
  esp8266_server.on("/setAcON", setAcON);
  esp8266_server.on("/setAcOFF", setAcOFF);
 // esp8266_server.onNotFound(handleNotFound);

  esp8266_server.begin();
  Serial.println("Start webserver ok...");

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  delay(2000);
  display.clearDisplay();
  display.setTextColor(WHITE);

  // Add the tasks to the scheduler
  runner.addTask(task0);
  runner.addTask(task1);
  runner.addTask(task2);
  runner.addTask(task3);
  // Enable the tasks
  task0.enable();
  task1.enable();
  task2.enable();
  task3.enable();
}

void loop() {
  runner.execute();
}

